package org.opens.javaskill.design;

import org.junit.Test;
import org.springframework.util.SerializationUtils;

import java.io.Serializable;
import java.util.stream.IntStream;

public class SingletonPattern {

    /**
     * 功能:
     *      测试静态内部类的加载时机.
     * 输出:
     *      RegisteredSingletonObject: 加载了
     *      heollo
     *      RegisteredSingletonObjectHolder: 加载了
     *      结束了
     * 说明:
     *      1. 可以发现, 在调用RegisteredSingletonObject的sayHello这个静态方法时, RegisteredSingletonObjectHolder类并没有
     *      被加载, 而是在调用getInstance方法时才被加载的, 这是jvm的一个特性: 静态内部类默认不加载, 只有在使用时才被加载;
     *          - 需要注意, 非静态内部类不允许有静态的东西, 因为非静态内部类依赖外部类的对象才能存在, 而在系统刚加载外部
     *          类但没有实例化对象时, 在逻辑上也不能允许非静态内部类被加载;
     *          - 静态内部类之所以可以这么做, 是因为静态内部类不依赖外部类的对象, 只依赖外部类的类, 并且是延迟加载, 这个
     *          可以比较完美的实现单例模式.
     */
    @Test
    public void registeredSingletonObjectTest() {
        System.out.println(RegisteredSingletonObject.sayHello());

        RegisteredSingletonObject instance = RegisteredSingletonObject.getInstance();
        System.out.println("结束了");
    }

    @Test
    public void testEnumSingletonObject() {
        EnumSingletonObject.INSTANCE.doSomething();
    }

    @Test
    public void testSerializable() {
        RegisteredSingletonObject instance = RegisteredSingletonObject.getInstance();
        byte[] serialize = SerializationUtils.serialize(instance);
        RegisteredSingletonObject deserializeObj = (RegisteredSingletonObject) SerializationUtils.deserialize(serialize);
        System.out.println(instance == deserializeObj);
        deserializeObj.sayByte();

        byte[] serialize1 = SerializationUtils.serialize(EnumSingletonObject.INSTANCE);
        EnumSingletonObject deserialize = (EnumSingletonObject) SerializationUtils.deserialize(serialize1);
        deserialize.doSomething();
    }

}

class SingletonObject {

    private static SingletonObject singletonObject;

    /**
     * 注意:
     *      1. 如果在非多线程环境下, 可以选择将synchronized锁去掉, 可以提升效率, 因为锁的开销比较大;
     *          - 但是去掉锁之后, 这个单例方法就不支持多线程了;
     *      2. 简单加上synchronized这样的饿汉式单例模式虽然保证了多线程下的单例, 但是不能保证性能, 如果获取单例对象的操作
     *      十分频繁, 就可以考虑使用其他方式的单例模式了.
     *          - 因为synchronized是一个重量级的锁;
     *
     */
    public static synchronized SingletonObject getInstance() {
        return singletonObject == null ? new SingletonObject() : singletonObject;
    }

}

/**
 * 功能:
 * <p>饿汉式单例</p>
 * 说明:
 * <pre>
 *     1. 当这个单例的实例非常大时, 饿汉式单例会在jvm启动时就加载这个类, 导致内存被占用.
 * </pre>
 */
class HungrySingletonObject {

    private static HungrySingletonObject singletonObject = new HungrySingletonObject();

    /**
     * 说明:
     *      1. 它基于 classloader 机制避免了多线程的同步问题，不过，instance 在类装载时就实例化，虽然导致类装载的原因有
     *      很多种，在单例模式中大多数都是调用 getInstance 方法， 但是也不能确定有其他的方式（或者其他的静态方法）导致类
     *      装载，这时候初始化 instance 显然没有达到 lazy loading 的效果。
     *      2. 再一个缺点就是: 没有懒加载, 浪费内存(感觉上单例模式很小, 并不会浪费很多);
     *      3. 由于没有加锁, 效率非常高; 并且写法非常简单;
     */
    public static synchronized HungrySingletonObject getInstance() {
        return singletonObject;
    }
}

/**
 * 功能:
 * <p>懒加载式单例</p>
 */
class DCLSingletonObject {

    private static volatile DCLSingletonObject singletonObject;

    public static DCLSingletonObject getInstance() {
        if(singletonObject == null) {
            /*
             * 说明:
             *      1. 给第二层锁加入同步锁, 但是同步锁之前还有一层判断, 也就是说: 这个同步锁只会在第一次创建单例的时候去
             *      使用锁, 在之后的过程中不会使用锁, 这样做极大地提高了单例模式的性能;
             *          - 这样也是懒加载, 节省了内存;
             *          - 同步锁保证了线程安全;
             *      2. 由于jit, 指令重排序的缘故, 这种模式可能会导致NullPointException;
             *          - 在return时, DCLSingletonObject的初始化还未完成的话就会触发这个异常.
             *          - 给singletonObject实例添加上volatile关键字即可保证在return时, DCLSingletonObject的初始化必然完成了, 因为
             *          volatile严格遵循happens before规则-在读之前必须先写一次, 并且volatile关键字会使编译器不去优化自己, 防止指令重排
             *          序导致的问题.
             *      3. 检查两次的做法成为double check, 是保障安全性时常用的一种手段.
             */
            synchronized (DCLSingletonObject.class) {
                if(singletonObject == null) {
                    singletonObject = new DCLSingletonObject();
                }
            }
        }
        return singletonObject;
    }

}

/**
 * 说明:
 *      1. 详情请查看下面的这个方法的注释: {@link org.opens.javaskill.design.SingletonPattern#registeredSingletonObjectTest()}
 *      2. 设计到外部类和内部类, 静态内部类的加载时机; 这种登记注册方式实现的单例模式很巧妙的利用了jvm的这一特性.
 */
class RegisteredSingletonObject implements Serializable {

    static {
        System.out.println("RegisteredSingletonObject: 加载了");
    }

    private class InnerClass {
        //非静态内部类不能拥有 静态变量 静态语句 静态方法 静态代码块, 会报错
        /*static {
            System.out.println("RegisteredSingletonObject: 加载了");
        }*/
    }

    private static class RegisteredSingletonObjectHolder {
        static {
            System.out.println("RegisteredSingletonObjectHolder: 加载了");
        }
        private static final RegisteredSingletonObject INSTANCE = new RegisteredSingletonObject();
    }

    private RegisteredSingletonObject() {}

    public void sayByte() {
        System.out.println("byte");
    }

    public static RegisteredSingletonObject getInstance() {
        return RegisteredSingletonObjectHolder.INSTANCE;
    }

    public static String sayHello() {
        return "heollo";
    }

}

/**
 * 说明:
 *      1. 这是使用枚举实现的单例模式, 书上说这是最好的实现方式, 并且可以避免反序列化攻击, 但是尝试了一下, 发现枚举也可以
 *      反序列化, {@link SingletonPattern#testSerializable()}
 */
enum EnumSingletonObject {

    INSTANCE;

    public void doSomething() {
        System.out.println("我是单例枚举的方法");
    }

}

class EnumSingletonObject2 {

    private EnumSingletonObject2() {

    }

    private enum Singleton {

        INSTANCE;

        private final EnumSingletonObject2 singletonObject2;


        Singleton() {
            singletonObject2 = new EnumSingletonObject2();
        }

        public EnumSingletonObject2 getInstance() {
            return INSTANCE.singletonObject2;
        }

    }

    public static EnumSingletonObject2 getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    public static void main(String[] args) {
        IntStream.rangeClosed(0, 10).forEach(x -> {
            new Thread(() -> {
                System.out.println(EnumSingletonObject2.getInstance());
            }).start();
        });
    }

}
